package com.aokang.footwear.common.handler;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.aokang.footwear.common.anno.RepeatSubmitAnno;
import com.aokang.footwear.common.enums.ErrorCodeEnum;
import com.aokang.footwear.common.i18n.ResultHelper;
import com.chiyoyo.redis.RedisUtil;
import com.chiyoyo.tokenhelper.LoginInfoDTO;
import com.chiyoyo.tokenhelper.TokenHelper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Web层日志切面,统一日志管理输出
 *
 * @author chilei
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Resource
    private RedisUtil redisUtil;
    /**
     * 需要打印响应报文的接口
     */
    private static final String PRINT_RESPONSE_URL_KEY = "java_server:print_response_url_list";
    /**
     * 需要打印的请求头
     */
    private static final String PRINT_HEADER_KEY = "java_server:print_header_list";

    @Around("within(com.aokang.*.*.web.controller..*)")
    public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
        List<Object> args = Arrays.stream(joinPoint.getArgs())
                .filter(arg -> (!(arg instanceof ServletRequest) && !(arg instanceof ServletResponse)))
                .collect(Collectors.toList());
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        long during = -1;
        if (requestAttributes == null) {
            //未登录或登录超时，请重新登录
            return ResultHelper.fail(ErrorCodeEnum.TOKEN_TIMEOUT);
        }
        Object obj = null;
        HttpServletRequest request = requestAttributes.getRequest();
        String requestUrl = request.getRequestURI().replaceAll("//", "/");
        LoginInfoDTO loginInfo = TokenHelper.getLoginInfo();

        try {
            if (repeatSubmit(joinPoint, requestUrl, loginInfo)) {
                long startTime = System.currentTimeMillis();
                obj = joinPoint.proceed(joinPoint.getArgs());
                during = System.currentTimeMillis() - startTime;
                return obj;
            } else {
                during = 0;
                //操作太频繁，请稍后再试
                return ResultHelper.fail(ErrorCodeEnum.REQUEST_REPEAT_SUBMIT);
            }
        } finally {
            try {
                args = args.stream().map(arg -> {
                    if (arg instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) arg;
                        // 返回文件名字符串
                        return "MultipartFile: " + file.getOriginalFilename();
                    } else {
                        // 返回原始参数
                        return arg;
                    }
                }).collect(Collectors.toList());
                StringBuilder sb = new StringBuilder(
                        String.format("[%s],[%s],params:%s", requestUrl, loginInfo, JSON.toJSONString(args)));
                JSONObject headerJson = new JSONObject();
                List<String> headerList = redisUtil.lRange(PRINT_HEADER_KEY, 0, -1);
                for (String header : headerList) {
                    headerJson.put(header, request.getHeader(header));
                }
                sb.append(String.format(",headers:[%s]", headerJson));
                List<String> urlList = redisUtil.lRange(PRINT_RESPONSE_URL_KEY, 0, -1);
                for (String url : urlList) {
                    if (Objects.equals(url, requestUrl)) {
                        sb.append(String.format(",response:[%s]", JSONObject.toJSONString(obj)));
                        break;
                    }
                }
                sb.append(String.format(",cost:[%s ms]", during));
                log.info(sb.toString());
            } catch (Exception e) {
                log.error("{} url log err:", requestUrl, e);
            }
        }
    }

    /**
     * 重复请求接口拦截
     *
     * @return 是否处理这次请求
     */
    public boolean repeatSubmit(ProceedingJoinPoint joinPoint, String requestUrl, LoginInfoDTO loginInfo) {
        boolean handle = true;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RepeatSubmitAnno repeatSubmitAnno = method.getAnnotation(RepeatSubmitAnno.class);
        //有注解再加锁
        if (repeatSubmitAnno != null) {
            String lockName = String.format("request_lock_%s_%s", requestUrl.replace("/", ""),
                    loginInfo.getUserid());
            //接口幂等，1秒内，一个人一个接口只能请求一次
            handle = redisUtil.setIfAbsent(lockName, lockName, repeatSubmitAnno.expireTime());
        }
        return handle;
    }
}
